﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace ZeroBug.EasyFlow.Core
{
    public class Calculator
    {
        private static readonly string pattern = @"\b\d+(\.\d+)?\b|sin|[\+\-\*\/\(\)]";

        private static readonly Dictionary<char, int> _operatorsPrecedence = new Dictionary<char, int>
        {
            { '(', 1 },
            { '+', 2 },
            { '-', 2 },
            { '*', 3 },
            { '/', 3 },
            { ')', 4 }
        };
        public static double Evaluate(string expression)
        {
            var stack = new Stack<double>();
            var operatorStack = new Stack<char>();

            var tokens = Tokenize(expression).ToList();

            foreach (var token in tokens)
            {
                if (double.TryParse(token, out double number))
                {
                    stack.Push(number);
                }
                else if (token.Length == 1)
                {
                    var operatorChar = token[0];

                    switch (operatorChar)
                    {
                        case '(':
                            operatorStack.Push(operatorChar);
                            break;
                        case ')':
                            while (operatorStack.Peek() != '(')
                            {
                                stack.Push(ApplyOperation(stack.Pop(), stack.Pop(), operatorStack.Pop()));
                            }
                            operatorStack.Pop(); // Remove '(' from the operator stack
                            break;
                        default:
                            while (operatorStack.Count > 0 && _operatorsPrecedence[operatorStack.Peek()] >= _operatorsPrecedence[operatorChar])
                            {
                                stack.Push(ApplyOperation(stack.Pop(), stack.Pop(), operatorStack.Pop()));
                            }
                            operatorStack.Push(operatorChar);
                            break;
                    }
                }
            }

            while (operatorStack.Count > 0)
            {
                stack.Push(ApplyOperation(stack.Pop(), stack.Pop(), operatorStack.Pop()));
            }

            return stack.Pop();
        }
        private static double ApplyOperation(double secondOperand, double firstOperand, char operatorChar)
        {
            switch (operatorChar)
            {
                case '+':
                    return firstOperand + secondOperand;
                case '-':
                    return firstOperand - secondOperand;
                case '*':
                    return firstOperand * secondOperand;
                case '/':
                    return firstOperand / secondOperand;
                default:
                    throw new InvalidOperationException("Invalid operator");
            }
        }
        private static IEnumerable<string> Tokenize(string expression)
        {
            MatchCollection matches = Regex.Matches(expression, pattern);

            foreach (Match match in matches)
            {
                yield return match.Value;
            }
        }
    }
}
